module module_HYDRO_utils
  use module_RT_data, only: rt_domain
  use config_base, only: nlst
  use iso_fortran_env, only: int64
#ifdef MPP_LAND
  use module_mpp_land, only: global_nx, global_ny, my_id, IO_id, &
       decompose_data_real, write_io_real, MPP_LAND_COM_REAL, &
       write_io_int, mpp_land_bcast_real, global_rt_nx, global_rt_ny, &
       decompose_rt_real, write_io_rt_real
  use MODULE_mpp_GWBUCKET, only: gw_decompose_real
#endif


  implicit none
  logical lr_dist_flag    !land routing distance calculated or not.

contains

  integer function get2d_real(var_name,out_buff,ix,jx,fileName)
    implicit none
#         include "netcdf.inc"
    integer :: ivar, iret,varid,ncid,ix,jx
    real out_buff(ix,jx)
    character(len=*), intent(in) :: var_name
    character(len=*), intent(in) :: fileName
    get2d_real = -1

    iret = nf_open(trim(fileName), NF_NOWRITE, ncid)
    if (iret .ne. 0) then
#ifdef HYDRO_D
       print*,"Failed to open the netcdf file: ",trim(fileName)
#endif
       out_buff = -9999.
       return
    endif
    ivar = nf_inq_varid(ncid,trim(var_name),  varid)
    if(ivar .ne. 0) then
       ivar = nf_inq_varid(ncid,trim(var_name//"_M"),  varid)
       if(ivar .ne. 0) then
#ifdef HYDRO_D
          write(6,*) "Read Error: could not find ",var_name
#endif
          return
       endif
    end if
    iret = nf_get_var_real(ncid, varid, out_buff)
    iret = nf_close(ncid)
    get2d_real =  ivar
  end function get2d_real


! this module create the distance dx, dy and diagnoal for routing
! 8 direction as the slop:
! 1: i,j+1
! 2: i+1, j+1
! 3: i+1, j
! 4: i+1, j-1
! 5: i, j-1
! 6: i-1, j-1
! 7: i-1, j
! 8: i-1, j+1
   real function get_dy(i,j,v,ix,jx)
      ! south north
       integer :: i,j,ix,jx
       real,dimension(ix,jx,9) :: v
       if( v(i,j,1) .le. 0) then
          get_dy = v(i,j,5)
       else if( v(i,j,5) .le. 0) then
          get_dy = v(i,j,1)
       else
          get_dy = (v(i,j,1) + v(i,j,5) ) / 2
       endif
       return
   end function get_dy

   real function get_dx(i,j,v,ix,jx)
      ! east-west
       integer :: i,j, ix,jx
       real,dimension(ix,jx,9) :: v
       if( v(i,j,3) .le. 0) then
          get_dx = v(i,j,7)
       else if( v(i,j,7) .le. 0) then
          get_dx = v(i,j,3)
       else
          get_dx = (v(i,j,3) + v(i,j,7) ) / 2
       endif
       return
   end function get_dx

   real function get_ll_d(lat1_in, lat2_in, lon1_in, lon2_in)
     implicit none
     real:: lat1, lat2, lon1, lon2
     real:: lat1_in, lat2_in, lon1_in, lon2_in
     real::  r, pai, a,c, dlat, dlon, b1,b2
     pai = 3.14159
     lat1 = lat1_in * pai/180
     lat2 = lat2_in * pai/180
     lon1 = lon1_in * pai/180
     lon2 = lon2_in * pai/180
     r = 6378.1*1000
     dlat = lat2 -lat1
     dlon = lon2 -lon1
     a = sin(dlat/2)*sin(dlat/2) + cos(lat1)*cos(lat2)*sin(dlon/2)*sin(dlon/2)
     b1 = sqrt(a)
     b2 = sqrt(1-a)
     c = 2.0*atan2(b1,b2)
     get_ll_d = R*c
     return

   end function get_ll_d

   real function get_ll_d_tmp(lat1_in, lat2_in, lon1_in, lon2_in)
     implicit none
     real:: lat1, lat2, lon1, lon2
     real:: lat1_in, lat2_in, lon1_in, lon2_in
     real::  r, pai
     pai = 3.14159
     lat1 = lat1_in * pai/180
     lat2 = lat2_in * pai/180
     lon1 = lon1_in * pai/180
     lon2 = lon2_in * pai/180
     r = 6371*1000
     get_ll_d_tmp = acos(sin(lat1)*sin(lat2)+cos(lat1)*cos(lat2)*cos(lon2-lon1))*r
     return

   end function get_ll_d_tmp

   subroutine get_rt_dxdy_ll(did)
!   use the land lat and lon to derive the routing distrt
      implicit none
      integer:: did, k
      integer iret
!     external get2d_real
!     real get2d_real
#ifdef MPP_LAND
      real, dimension(global_rt_nx,global_rt_ny):: latrt, lonrt
      real, dimension(global_rt_nx,global_rt_ny,9):: dist
      if(my_id .eq. IO_id) then
 ! read the lat and lon.
         iret =  get2d_real("LONGITUDE",lonrt,global_rt_nx,global_rt_ny,&
                     trim(nlst(did)%GEO_FINEGRID_FLNM ))
         iret =  get2d_real("LATITUDE",latrt,global_rt_nx,global_rt_ny,&
                     trim(nlst(did)%GEO_FINEGRID_FLNM ))
         call get_dist_ll(dist,latrt,lonrt,global_rt_nx,global_rt_ny)
      end if
     do k = 1 , 9
        call decompose_RT_real(dist(:,:,k),rt_domain(did)%overland%properties%distance_to_neighbor(:,:,k), &
                global_rt_nx,global_rt_ny,rt_domain(did)%ixrt,rt_domain(did)%jxrt)
     end do
#else
      real, dimension(rt_domain(did)%ixrt,rt_domain(did)%jxrt):: latrt, lonrt
 ! read the lat and lon.
         iret =  get2d_real("LONGITUDE",lonrt,rt_domain(did)%ixrt,rt_domain(did)%jxrt,&
                     trim(nlst(did)%GEO_FINEGRID_FLNM ))
         iret =  get2d_real("LATITUDE",latrt,rt_domain(did)%ixrt,rt_domain(did)%jxrt,&
                     trim(nlst(did)%GEO_FINEGRID_FLNM ))
         call get_dist_ll(rt_domain(did)%overland%properties%distance_to_neighbor,latrt,lonrt,rt_domain(did)%ixrt,rt_domain(did)%jxrt)
#endif

   end subroutine get_rt_dxdy_ll

!  get dx and dy of lat and lon
   subroutine get_dist_ll(dist,lat,lon,ix,jx)
      implicit none
      integer:: ix,jx
      real, dimension(ix,jx,9):: dist
      real, dimension(ix,jx):: lat, lon
      integer:: i,j
      real x,y
      dist = -1
      do j = 1, jx
        do i = 1, ix
          if(j .lt. jx) dist(i,j,1) = &
             get_ll_d(lat(i,j), lat(i,j+1), lon(i,j), lon(i,j+1))
          if(j .lt. jx .and. i .lt. ix) dist(i,j,2) =  &
             get_ll_d(lat(i,j), lat(i+1,j+1), lon(i,j), lon(i+1,j+1))
          if(i .lt. ix) dist(i,j,3) = &
             get_ll_d(lat(i,j), lat(i+1,j), lon(i,j), lon(i+1,j))
          if(j .gt. 1 .and. i .lt. ix) dist(i,j,4) = &
             get_ll_d(lat(i,j), lat(i+1,j-1), lon(i,j), lon(i+1,j-1))
          if(j .gt. 1 ) dist(i,j,5) = &
             get_ll_d(lat(i,j), lat(i,j-1), lon(i,j), lon(i,j-1))
          if(j .gt. 1 .and. i .gt. 1) dist(i,j,6) = &
             get_ll_d(lat(i,j), lat(i-1,j-1), lon(i,j), lon(i-1,j-1))
          if(i .gt. 1) dist(i,j,7) = &
             get_ll_d(lat(i,j), lat(i-1,j), lon(i,j), lon(i-1,j))
          if(j .lt. jx .and. i .gt. 1) dist(i,j,8) = &
             get_ll_d(lat(i,j), lat(i-1,j+1), lon(i,j), lon(i-1,j+1))
        end do
      end do
      do j = 1, jx
        do i = 1, ix
            if(j.eq.1) then
               y =  get_ll_d(lat(i,j), lat(i,j+1), lon(i,j), lon(i,j+1))
            else if(j.eq.jx) then
               y =  get_ll_d(lat(i,j-1), lat(i,j), lon(i,j-1), lon(i,j))
            else
               y =  get_ll_d(lat(i,j-1), lat(i,j+1), lon(i,j-1), lon(i,j+1))/2.0
            endif

            if(i.eq.ix) then
                x =  get_ll_d(lat(i,j), lat(i-1,j), lon(i,j), lon(i-1,j))
            else if(i.eq.1) then
                x =  get_ll_d(lat(i,j), lat(i+1,j), lon(i,j), lon(i+1,j))
            else
                x =  get_ll_d(lat(i-1,j), lat(i+1,j), lon(i-1,j), lon(i+1,j))/2.0
            endif
            dist(i,j,9) = x * y
        end do
      end do
#ifdef HYDRO_D
      write(6,*) "finished get_dist_ll"
#endif
   end subroutine get_dist_ll

!  get dx and dy of map projected
   subroutine get_dxdy_mp(dist,ix,jx,dx,dy)
      implicit none
      integer:: ix,jx
      real :: dx,dy
      integer:: i,j
      real :: v1
      ! out variable
      real, dimension(ix,jx,9)::dist
      dist = -1
      v1 = sqrt(dx*dx + dy*dy)
      do j = 1, jx
        do i = 1, ix
          if(j .lt. jx) dist(i,j,1) = dy
          if(j .lt. jx .and. i .lt. ix) dist(i,j,2) = v1
          if(i .lt. ix) dist(i,j,3) = dx
          if(j .gt. 1 .and. i .lt. ix) dist(i,j,4) = v1
          if(j .gt. 1 ) dist(i,j,5) = dy
          if(j .gt. 1 .and. i .gt. 1) dist(i,j,6) = v1
          if(i .gt. 1) dist(i,j,7) = dx
          if(j .lt. jx .and. i .gt. 1) dist(i,j,8) = v1
          dist(i,j,9) = dx * dy
        end do
      end do
#ifdef HYDRO_D
      write(6,*) "finished get_dxdy_mp "
#endif
   end subroutine get_dxdy_mp

   subroutine get_dist_lsm(did)
     integer did
#ifdef MPP_LAND
     integer ix,jx,ixrt,jxrt, k
     real , dimension(global_nx,global_ny):: latitude,longitude
     real, dimension(global_nx,global_ny,9):: dist
     if(nlst(did)%dxrt0 .lt. 0) then
           ! lat and lon grid
          call write_io_real(rt_domain(did)%lat_lsm,latitude)
          call write_io_real(rt_domain(did)%lon_lsm,longitude)
          if(my_id.eq.IO_id) then
               call get_dist_ll(dist,latitude,longitude,  &
                         global_nx,global_ny)
          endif

     else
           ! mapp projected grid.
          if(my_id.eq.IO_id) then
              call get_dxdy_mp(dist,global_nx,global_ny, &
                 nlst(did)%dxrt0*nlst(did)%AGGFACTRT,nlst(did)%dxrt0*nlst(did)%AGGFACTRT)
          endif
     endif
     do k = 1 , 9
        call decompose_data_real(dist(:,:,k),rt_domain(did)%dist_lsm(:,:,k))
     end do
#else
     if(nlst(did)%dxrt0 .lt. 0) then
        ! lat and lon grid
        call get_dist_ll(rt_domain(did)%dist_lsm,rt_domain(did)%lat_lsm,rt_domain(did)%lon_lsm,  &
                      rt_domain(did)%ix,rt_domain(did)%jx)
     else
        ! mapp projected grid.
        call get_dxdy_mp(rt_domain(did)%dist_lsm,rt_domain(did)%ix,rt_domain(did)%jx, &
              nlst(did)%dxrt0*nlst(did)%AGGFACTRT,nlst(did)%dxrt0*nlst(did)%AGGFACTRT)
     endif
#endif


   end subroutine get_dist_lsm

   subroutine get_dist_lrt(did)
     integer did, k

!     real :: tmp_dist(global_rt_nx, global_rt_ny,9)

! calculate the distance for land routing from the lat /lon of land surface model
     if(nlst(did)%dxrt0 .lt. 0) then
        ! using lat and lon grid when channel routing is off
        call get_rt_dxdy_ll(did)
     else
        ! mapp projected grid.
         call get_dxdy_mp(rt_domain(did)%overland%properties%distance_to_neighbor,rt_domain(did)%ixrt,rt_domain(did)%jxrt, &
              nlst(did)%dxrt0,nlst(did)%dxrt0)
#ifdef MPP_LAND
        do k = 1, 9
           call MPP_LAND_COM_REAL(rt_domain(did)%overland%properties%distance_to_neighbor(:,:,k),rt_domain(did)%IXRT,rt_domain(did)%JXRT,99)
        end do
#endif
     endif


   end subroutine get_dist_lrt

!   subroutine get_dist_crt(did)
!      integer did, k
! calculate the distance from channel routing
!     if(nlst_rt(did)%dxrt0 .lt. 0) then
!        ! lat and lon grid
!        if(rt_domain(did)%overland%properties%distance_to_neighbor(1,1,9) .eq. -999)   &
!           call get_dist_ll(rt_domain(did)%overland%properties%distance_to_neighbor,rt_domain(did)%latval,rt_domain(did)%lonval,  &
!                      rt_domain(did)%ixrt,rt_domain(did)%jxrt)
!     else
!        ! mapp projected grid.
!        if(rt_domain(did)%overland%properties%distance_to_neighbor(1,1,9) .eq. -999)   &
!           call get_dxdy_mp(rt_domain(did)%overland%properties%distance_to_neighbor,rt_domain(did)%ixrt,rt_domain(did)%jxrt, &
!              nlst_rt(did)%dxrt0,nlst_rt(did)%dxrt0)
!     endif
!#ifdef MPP_LAND
!     do k = 1, 9
!       call MPP_LAND_COM_REAL(rt_domain(did)%overland%properties%distance_to_neighbor(:,:,k),rt_domain(did)%IXRT,rt_domain(did)%JXRT,99)
!     end do
!#endif
!   end subroutine get_dist_crt

   subroutine get_basn_area(did)
      implicit none
      integer :: did, ix,jx, k
      real :: basns_area(rt_domain(did)%gnumbasns)
#ifdef MPP_LAND
      integer :: mask(global_nx, global_ny)
      real :: dist_lsm(global_nx, global_ny,9)
#else
      integer :: mask(rt_domain(did)%ix, rt_domain(did)%jx)
      real :: dist_lsm(rt_domain(did)%ix, rt_domain(did)%jx,9)
#endif
#ifdef MPP_LAND
      ix = global_nx
      jx = global_ny
      call write_IO_int(rt_domain(did)%GWSUBBASMSK,mask)
      do k = 1,  9
         call write_IO_real(rt_domain(did)%dist_lsm(:,:,k),dist_lsm(:,:,k)) 
      end do
#else
      ix = rt_domain(did)%ix
      jx = rt_domain(did)%jx
      mask = rt_domain(did)%GWSUBBASMSK
      dist_lsm = rt_domain(did)%dist_lsm
#endif

#ifdef MPP_LAND
      if(my_id .eq. IO_id) then
         call get_area_g(basns_area,mask, rt_domain(did)%gnumbasns,ix,jx,dist_lsm)
      end if
!      call mpp_land_bcast_real(rt_domain(did)%numbasns,rt_domain(did)%basns_area)

      call gw_decompose_real(rt_domain(did)%gnumbasns, rt_domain(did)%numbasns, &
           rt_domain(did)%basnsInd, basns_area,rt_domain(did)%basns_area)
#else
      call get_area_g(rt_domain(did)%basns_area,mask, rt_domain(did)%gnumbasns,ix,jx,dist_lsm)
#endif
   end subroutine get_basn_area

   subroutine get_area_g(basns_area,GWSUBBASMSK, numbasns,ix,jx,dist)
      integer :: i,j, n, ix,jx, numbasns
      integer :: count(numbasns)
      real :: basns_area(numbasns) , dist(ix,jx,9)
      integer :: GWSUBBASMSK(ix,jx)
      basns_area = 0
      count = 0
      do  j = 1, jx
        do  i = 1, ix
           n = GWSUBBASMSK(i,j)
           if(n .gt. 0) then
              basns_area(n) = basns_area(n)+dist(i,j,9)
              count(n) = count(n) + 1
           endif
        end do
      end do
      do i = 1, numbasns
         if(count(i) .gt. 0) then
             basns_area(i) = basns_area(i) / count(i)
         end if
      end do
   end subroutine get_area_g

    subroutine get_area_g8(basns_area,GWSUBBASMSK, numbasns,ix,jx,dist)
        integer :: i,j, n, ix,jx, numbasns
        integer :: count(numbasns)
        real :: basns_area(numbasns) , dist(ix,jx,9)
        integer(kind=int64) :: GWSUBBASMSK(ix,jx)
        basns_area = 0
        count = 0
        do  j = 1, jx
            do  i = 1, ix
                n = GWSUBBASMSK(i,j)
                if(n .gt. 0) then
                    basns_area(n) = basns_area(n)+dist(i,j,9)
                    count(n) = count(n) + 1
                endif
            end do
        end do
        do i = 1, numbasns
            if(count(i) .gt. 0) then
                basns_area(i) = basns_area(i) / count(i)
            end if
        end do
    end subroutine get_area_g8

   subroutine get_node_area(did)
       integer :: did

       call get_area_g8(rt_domain(did)%node_area, rt_domain(did)%CH_NETLNK, &
         rt_domain(did)%NLINKS,rt_domain(did)%ixrt, rt_domain(did)%jxrt, rt_domain(did)%overland%properties%distance_to_neighbor)
   end subroutine get_node_area


end module module_HYDRO_utils
